package pool;

import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.Arrays;

/**
 * @author zy.du
 * @date 2020/11/11 2:16 PM
 */
public class Connection {
    private Socket sock;
    private InetSocketAddress inetSockAddr;
    private Long lastAccessTime = System.currentTimeMillis();

    private boolean needActiveTest = false;

    public Connection(Socket sock, InetSocketAddress inetSockAddr) {
        this.sock = sock;
        this.inetSockAddr = inetSockAddr;
    }

    public void close() throws IOException {
        //若启用pool池，则
        if (true) {
            ConnectionPool.closeConnection(this);
        } else {
            this.closeDirectly();
        }
    }

    public void release() throws IOException {
        if (true) {
            ConnectionPool.releaseConnection(this);
        } else {
            this.closeDirectly();
        }
    }

    /**
     * force close socket,
     */
    public void closeDirectly() throws IOException {
        if (this.sock != null) {
            try {
                closeSocket(this.sock);
            } finally {
                this.sock = null;
            }
        }
    }

    public boolean activeTest() throws IOException {
        if (this.sock == null) {
            return false;
        }
        return activeTest(this.sock);
    }

    private boolean activeTest(Socket sock) throws IOException {
        byte[] header = packHeader((byte)111, 0L, (byte)0);
        sock.getOutputStream().write(header);
        RecvHeaderInfo headerInfo = recvHeader(sock.getInputStream(), (byte)100, 0L);
        return headerInfo.errno == 0;
    }

    public static class RecvHeaderInfo {
        public byte errno;
        public long body_len;

        public RecvHeaderInfo(byte errno, long body_len) {
            this.errno = errno;
            this.body_len = body_len;
        }
    }


    private RecvHeaderInfo recvHeader(InputStream in, byte expect_cmd, long expect_body_len) throws IOException {
        byte[] header = new byte[10];
        int bytes;
        if ((bytes = in.read(header)) != header.length) {
            throw new IOException("recv package size " + bytes + " != " + header.length);
        } else if (header[8] != expect_cmd) {
            throw new IOException("recv cmd: " + header[8] + " is not correct, expect cmd: " + expect_cmd);
        } else if (header[9] != 0) {
            return new RecvHeaderInfo(header[9], 0L);
        } else {
            long pkg_len = buff2long(header, 0);
            if (pkg_len < 0L) {
                throw new IOException("recv body length: " + pkg_len + " < 0!");
            } else if (expect_body_len >= 0L && pkg_len != expect_body_len) {
                throw new IOException("recv body length: " + pkg_len + " is not correct, expect length: " + expect_body_len);
            } else {
                return new RecvHeaderInfo((byte)0, pkg_len);
            }
        }
    }



    private int buff2int(byte[] bs, int offset) {
        return (bs[offset] >= 0 ? bs[offset] : 256 + bs[offset]) << 24 | (bs[offset + 1] >= 0 ? bs[offset + 1] : 256 + bs[offset + 1]) << 16 | (bs[offset + 2] >= 0 ? bs[offset + 2] : 256 + bs[offset + 2]) << 8 | (bs[offset + 3] >= 0 ? bs[offset + 3] : 256 + bs[offset + 3]);
    }

    private long buff2long(byte[] bs, int offset) {
        return (long)(bs[offset] >= 0 ? bs[offset] : 256 + bs[offset]) << 56 | (long)(bs[offset + 1] >= 0 ? bs[offset + 1] : 256 + bs[offset + 1]) << 48 | (long)(bs[offset + 2] >= 0 ? bs[offset + 2] : 256 + bs[offset + 2]) << 40 | (long)(bs[offset + 3] >= 0 ? bs[offset + 3] : 256 + bs[offset + 3]) << 32 | (long)(bs[offset + 4] >= 0 ? bs[offset + 4] : 256 + bs[offset + 4]) << 24 | (long)(bs[offset + 5] >= 0 ? bs[offset + 5] : 256 + bs[offset + 5]) << 16 | (long)(bs[offset + 6] >= 0 ? bs[offset + 6] : 256 + bs[offset + 6]) << 8 | (long)(bs[offset + 7] >= 0 ? bs[offset + 7] : 256 + bs[offset + 7]);
    }


    private void closeSocket(Socket sock) throws IOException {
        byte[] header = packHeader((byte)82, 0L, (byte)0);
        sock.getOutputStream().write(header);
        sock.close();
    }

    private byte[] packHeader(byte cmd, long pkg_len, byte errno) throws UnsupportedEncodingException {
        byte[] header = new byte[10];
        Arrays.fill(header, (byte)0);
        byte[] hex_len = long2buff(pkg_len);
        System.arraycopy(hex_len, 0, header, 0, hex_len.length);
        header[8] = cmd;
        header[9] = errno;
        return header;
    }

    private byte[] long2buff(long n) {
        byte[] bs = new byte[]{(byte)((int)(n >> 56 & 255L)), (byte)((int)(n >> 48 & 255L)), (byte)((int)(n >> 40 & 255L)), (byte)((int)(n >> 32 & 255L)), (byte)((int)(n >> 24 & 255L)), (byte)((int)(n >> 16 & 255L)), (byte)((int)(n >> 8 & 255L)), (byte)((int)(n & 255L))};
        return bs;
    }


    public boolean isConnected() {
        boolean isConnected = false;
        if (sock != null) {
            if (sock.isConnected()) {
                isConnected = true;
            }
        }
        return isConnected;
    }
    public boolean isAvaliable() {
        if (isConnected()) {
            if (sock.getPort() == 0) {
                return false;
            }
            if (sock.getInetAddress() == null) {
                return false;
            }
            if (sock.getRemoteSocketAddress() == null) {
                return false;
            }
            if (sock.isInputShutdown()) {
                return false;
            }
            if (sock.isOutputShutdown()) {
                return false;
            }
            return true;
        }
        return false;
    }


    public Socket getSock() {
        return sock;
    }

    public void setSock(Socket sock) {
        this.sock = sock;
    }

    public InetSocketAddress getInetSockAddr() {
        return inetSockAddr;
    }

    public void setInetSockAddr(InetSocketAddress inetSockAddr) {
        this.inetSockAddr = inetSockAddr;
    }

    public Long getLastAccessTime() {
        return lastAccessTime;
    }

    public void setLastAccessTime(Long lastAccessTime) {
        this.lastAccessTime = lastAccessTime;
    }

    public boolean isNeedActiveTest() {
        return needActiveTest;
    }

    public void setNeedActiveTest(boolean needActiveTest) {
        this.needActiveTest = needActiveTest;
    }
}
